Skip to content

Add policy hub and platform gateway support for portals#1266

Merged
AnuGayan merged 51 commits intowso2:mainfrom
AnuGayan:main-platform
Mar 20, 2026
Merged

Add policy hub and platform gateway support for portals#1266
AnuGayan merged 51 commits intowso2:mainfrom
AnuGayan:main-platform

Conversation

@AnuGayan
Copy link
Copy Markdown
Contributor

@AnuGayan AnuGayan commented Mar 8, 2026

This pull request introduces enhancements for platform gateway support and improves the clarity and consistency of gateway-related UI messages in both English and French locale files, as well as configuration updates. The main changes are grouped into platform gateway additions, UI/UX improvements, and configuration updates.

Platform Gateway Support:

  • Added new configuration section platformGateway in settings.json to support platform gateway features, including release URL, version, and control plane host.
  • Introduced platform gateway-related UI strings in both en.json and fr.json, such as platform gateway details, status labels, configuration fields, and registration token dialogs. [1] [2] [3] [4]

UI/UX Improvements for Gateway Management:

  • Improved gateway environment management messages, including error handling, validation, and helper texts for display name and URL fields. [1] [2] [3]
  • Updated gateway environment permission messages for clarity and removed formatting issues.
  • Refined gateway search and list UI strings for compactness and clarity, and reorganized tab and button labels for gateway environments. [1] [2]

Configuration and Documentation Updates:

  • Updated docUrl in settings.json and added new platform gateway configuration fields.

These changes collectively improve platform gateway integration, enhance user feedback and validation, and provide a more consistent and user-friendly gateway management experience.

Admin Portal

Screen.Recording.2026-03-14.at.9.23.41.AM.mov

Publisher Portal

Screen.Recording.2026-03-16.at.9.53.23.PM.mov

Copilot AI review requested due to automatic review settings March 8, 2026 05:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Platform Gateway management (admin UI, routes, API client methods, token flows) and a Policy Hub client plus integration in publisher (policy resolution, category filtering, UI flags). Also updates gateway types, theme, and numerous locale strings across portals.

Changes

Cohort / File(s) Summary
Admin — Config & Locales
portals/admin/src/main/webapp/site/public/conf/settings.json, portals/admin/src/main/webapp/site/public/locales/en.json, portals/admin/src/main/webapp/site/public/locales/fr.json
Added platformGateway config and many platform-gateway UI localization keys.
Admin — Platform Gateway UI & Routes
portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/PlatformGatewayManagement.jsx, .../AddEditGWEnvironment.jsx, .../ListGWEnviornments.jsx, .../index.jsx, .../Base/RouteMenuMapping.jsx
New PlatformGatewayManagement page and QuickStartGuide; Add/Edit flows extended to support platform gateways, token regeneration, status rendering, and new routes/navigation entries.
Admin — API Client
portals/admin/src/main/webapp/source/src/app/data/api.js
Added platform-gateway API methods (getPlatformGatewayList, createPlatformGateway, deletePlatformGateway, regeneratePlatformGatewayToken); duplicate insertions present in diff.
Admin — Layout Tweak
portals/admin/src/main/webapp/source/src/app/components/AdminPages/Addons/ListBase.jsx
Adjusted toolbar Grid item to use flex styling for action alignment.
Publisher — Config & Locales
portals/publisher/src/main/webapp/site/public/conf/settings.json, portals/publisher/src/main/webapp/site/public/locales/en.json, portals/publisher/src/main/webapp/site/public/locales/fr.json
Added policyHub settings and small locale additions for policies UI (e.g., "All", category filter).
Publisher — Gateway Types & Theme
portals/publisher/src/main/webapp/source/src/app/data/Constants.js, .../data/defaultTheme.js
Replaced AWS gateway type with apiPlatform and added platform gateway gradient to theme.
Publisher — API Create / Metadata
portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/.../APICreateRoutes.jsx, .../APICreateWithAI.jsx, .../NewOverview/MetaData.jsx
Added Platform Gateway entries to gateway selection and display logic.
Publisher — Policy Hub module
portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js
New PolicyHub client (listing, fetching definitions/specs, parsing, caching, utilities to convert hub policies to local specs).
Publisher — Policy Hub Integration
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/Policies.tsx, .../PoliciesExpansion.tsx, .../PolicyConfigurationEditDrawer.tsx, .../ViewPolicy.tsx
When gateway = apiPlatform, fetch/resolve policy specs from PolicyHub (with fallbacks), adapt resolution and fetch flows to use PolicyHub.
Publisher — Policy UI, Filtering & Flags
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx, .../PolicyForm/GeneralDetails.tsx, .../PolicyForm/PolicyViewForm.tsx, .../PolicyForm/SourceDetails.tsx, .../PoliciesExpansion.tsx
Added category-based filtering UI/logic, apiSubType propagation, hideFlowsAndApiTypes flag, and conditional rendering of fields based on gateway/policy type.
Publisher — Policy Components & Types
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyCard.tsx, .../Shared/PoliciesUI/AttachedPolicyCard.tsx, .../Types.d.ts
Added optional showDownload prop to control download button visibility; added category?/categories? to Policy type and supportedGateways? to AttachedPolicy type.
Publisher — Attached Policy Behavior
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyCard.tsx
Hide download button for PolicyHub-sourced policies (showDownload=false) and removed unused UI imports.
Devportal — Locales Rearrangement
portals/devportal/src/main/webapp/site/public/locales/en.json
Reordered a few WSDL-related locale keys (no content changes).

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant AdminUI as Admin Portal UI
    participant API as Backend API
    participant PlatformGW as Platform Gateway Service

    rect rgba(200,230,255,0.5)
    User->>AdminUI: Open "Manage Platform Gateways"
    AdminUI->>API: GET /gateways
    API-->>AdminUI: Return gateway list (includes platformGatewayId,isActive)
    AdminUI->>User: Render list with status
    end

    rect rgba(200,255,200,0.5)
    User->>AdminUI: Edit platform gateway
    AdminUI->>API: GET /gateway-environments/:envId
    API-->>AdminUI: platformGateway details
    AdminUI->>User: Show edit form
    end

    rect rgba(255,230,200,0.5)
    User->>AdminUI: Regenerate token
    AdminUI->>API: POST /gateways/{gatewayId}/regenerate-token
    API->>PlatformGW: Trigger regeneration
    PlatformGW-->>API: Return new token
    API-->>AdminUI: New token
    AdminUI->>User: Display token commands
    end
Loading
sequenceDiagram
    participant User as User
    participant PublisherUI as Publisher Portal UI
    participant PolicyHub as Policy Hub Service
    participant API as Backend API

    rect rgba(220,200,255,0.5)
    User->>PublisherUI: Open API Policies (gateway = apiPlatform)
    PublisherUI->>PolicyHub: listAllPolicySpecs()
    PolicyHub-->>PublisherUI: Policy specs
    PublisherUI->>User: Display policies + category filter
    end

    rect rgba(200,255,230,0.5)
    User->>PublisherUI: Select category filter
    PublisherUI->>PublisherUI: Filter policies client-side
    PublisherUI->>User: Show filtered policies
    end

    rect rgba(255,220,220,0.5)
    User->>PublisherUI: View policy details
    PublisherUI->>PolicyHub: getPolicySpec(name,version)
    PolicyHub-->>PublisherUI: Return policy spec
    PublisherUI->>User: Show policy (download hidden for Hub policies)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through configs, tokens, and theme,

Wove QuickStart steps and PolicyHub dreams,
Categories sorted, tokens anew,
Gateways aligned — a fresh path to pursue,
The rabbit applauds this tidy stream!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add policy hub and platform gateway support for portals' directly and clearly describes the main objective of the changeset, which is to add support for both policy hub and platform gateway features across the admin and publisher portals.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces platform gateway support across the admin and publisher portals. It adds a new "Platform Gateway" type to the gateway environment management system, integrates a "Policy Hub" for managing policies associated with platform gateways in the publisher portal, and provides new UI for registering, configuring, and managing platform gateways in the admin portal.

Changes:

  • Added Policy Hub integration in the publisher portal for fetching and displaying policies from an external policy hub service for platform gateway APIs, including category-based filtering of policies.
  • Added platform gateway management endpoints and UI in the admin portal (list, create, delete, regenerate token) with a new detail/edit view for platform gateways.
  • Added platform-gateway as a new gateway type option across API creation flows, overview display, and internationalization files.

Reviewed changes

Copilot reviewed 26 out of 28 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
portals/publisher/.../Constants.js Replaced AWS gateway type with apiPlatform: 'api-platform'
portals/publisher/.../defaultTheme.js Added platform gateway gradient color
portals/publisher/.../PolicyList.tsx Added category-based policy filtering with multi-select dropdown
portals/publisher/.../Policies.tsx Integrated PolicyHub for fetching policies when API uses platform gateway
portals/publisher/.../PoliciesExpansion.tsx Added resolvePolicySpec helper for PolicyHub-based policy resolution
portals/publisher/.../PolicyConfigurationEditDrawer.tsx Added PolicyHub fallback for fetching policy specs
portals/publisher/.../ViewPolicy.tsx Added PolicyHub-based policy spec retrieval
portals/publisher/.../PolicyViewForm.tsx Conditionally hides source details and attributes for PolicyHub policies
portals/publisher/.../GeneralDetails.tsx Added hideFlowsAndApiTypes prop for PolicyHub policies
portals/publisher/.../SourceDetails.tsx Hides policy download for PolicyHub gateway policies
portals/publisher/.../AttachedPolicyCard.tsx (shared) Added showDownload prop to conditionally show download button
portals/publisher/.../AttachedPolicyCard.tsx (policies) Uses showDownload to hide download for PolicyHub policies
portals/publisher/.../MetaData.jsx Shows "Platform Gateway" label for platform gateway type
portals/publisher/.../APICreateWithAI.jsx Added platform-gateway to gateway details map
portals/publisher/.../APICreateRoutes.jsx Added platform-gateway to gateway details map
portals/publisher/.../Types.d.ts Added category, categories, supportedGateways fields
portals/publisher/.../settings.json Added policyHub configuration section
portals/publisher/.../en.json Added category filter translation key
portals/admin/.../api.js Added CRUD methods for platform gateways
portals/admin/.../ListGWEnviornments.jsx Added status column, platform gateway detection, custom edit button
portals/admin/.../AddEditGWEnvironment.jsx Added platform gateway detail/edit view with token regeneration
portals/admin/.../index.jsx Added routes for PlatformGatewayManagement
portals/admin/.../RouteMenuMapping.jsx Added Platform Gateways navigation entry
portals/admin/.../ListBase.jsx Layout fix for button alignment
portals/admin/.../settings.json Added platformGateway configuration section
portals/admin/.../en.json Added new translation keys for platform gateways
portals/admin/.../fr.json Added new translation keys for platform gateways
portals/admin/package-lock.json Trailing newline fix

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (1)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx (1)

124-153: Consider adding selectedCategories to the dependency array or using functional state update.

The useEffect reads selectedCategories (lines 126, 132, 149-150) but doesn't include it in the dependency array. This can lead to stale closures. While the current implementation works due to the comparison guard before setSelectedCategories, the ESLint exhaustive-deps rule would flag this.

Consider using a functional state update pattern to avoid the stale closure issue:

Proposed refactor using functional update
     useEffect(() => {
         if (availableCategories.length === 0) {
-            if (selectedCategories.length > 0) {
-                setSelectedCategories([]);
-            }
+            setSelectedCategories((prev) => (prev.length > 0 ? [] : prev));
             return;
         }
 
-        const normalizedSelection = selectedCategories.filter((category) =>
-            availableCategories.includes(category),
-        );
-        let nextSelection = normalizedSelection;
-
-        if (nextSelection.length === 0) {
-            const defaults = isAIAPI
-                ? ['AI', 'Guardrails']
-                : (isRestAPI ? ['Transformation', 'Security'] : []);
-            const matchingDefaults = defaults.filter((category) =>
-                availableCategories.includes(category),
-            );
-            if (matchingDefaults.length > 0) {
-                nextSelection = matchingDefaults;
+        setSelectedCategories((prev) => {
+            const normalizedSelection = prev.filter((category) =>
+                availableCategories.includes(category),
+            );
+            let nextSelection = normalizedSelection;
+
+            if (nextSelection.length === 0) {
+                const defaults = isAIAPI
+                    ? ['AI', 'Guardrails']
+                    : (isRestAPI ? ['Transformation', 'Security'] : []);
+                const matchingDefaults = defaults.filter((category) =>
+                    availableCategories.includes(category),
+                );
+                if (matchingDefaults.length > 0) {
+                    nextSelection = matchingDefaults;
+                }
             }
-        }
 
-        if (nextSelection.length !== selectedCategories.length
-            || nextSelection.some((category, index) => category !== selectedCategories[index])) {
-            setSelectedCategories(nextSelection);
-        }
+            if (nextSelection.length !== prev.length
+                || nextSelection.some((category, index) => category !== prev[index])) {
+                return nextSelection;
+            }
+            return prev;
+        });
     }, [availableCategories, isAIAPI, isRestAPI]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`
around lines 124 - 153, The effect reads selectedCategories but doesn't include
it in dependencies, risking stale closures and ESLint warnings; update the logic
in the useEffect (the useEffect that references availableCategories, isAIAPI,
isRestAPI, selectedCategories, and calls setSelectedCategories) to use a
functional state update: call setSelectedCategories(prev => { compute
normalizedSelection, nextSelection and compare against prev, returning prev or
the new array }) so the current selectedCategories value is read from prev
instead of the closed-over variable; alternatively, if you prefer not to use a
functional updater, add selectedCategories to the dependency array of the
useEffect to satisfy exhaustive-deps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx`:
- Around line 337-341: The added UI and alert strings in
AddEditGWEnvironment.jsx (e.g., the Alert.success/Alert.error messages like
"Gateway registration token regenerated successfully." and "Failed to regenerate
gateway registration token.", and other texts such as "Loading gateway
details...", "Back to Gateways", "Gateway Name", "Generate Key") are hardcoded
English; replace each hardcoded literal with the project's localization API
(e.g., use intl.formatMessage, t(), or the existing i18n helper used elsewhere
in the codebase) and reference appropriate message IDs from the locale bundles
added in this PR; update the Alert.success and Alert.error calls to pass
localized strings instead of raw literals, and ensure all other occurrences in
the file (ranges noted around the diff and lines ~363-364, 404-408, 816-1086)
are converted to use the same localization mechanism so the page respects the
locale bundles.
- Around line 367-400: The call to updateGatewayEnvironment in
AddEditGWEnvironment.jsx omits the provider argument so it falls back to the
default ('wso2') and can overwrite api-platform environments; when performing
header-only saves preserve the existing provider value used in formSaveCallback
by passing the provider through to updateGatewayEnvironment (or by reading it
from the current environment object) so the provider is included after the other
DTOs (additionalPropertiesArrayDTO, permissionsDTO, vhostDTO) in the argument
list; ensure the same provider symbol/name used in formSaveCallback is used here
so provider is not lost on save.

In
`@portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/ListGWEnviornments.jsx`:
- Around line 101-112: The code currently defaults gatewayStatus to 'Active' and
only overwrites it for platform gateways, causing non-platform rows to
incorrectly show a green "Active" chip; change the logic in
ListGWEnviornments.jsx so gatewayStatus starts as null/undefined and is assigned
only when additionalProperties.isActive is explicitly present (e.g., set
gatewayStatus = additionalProperties.isActive === 'true' ? 'Active' : 'Inactive'
only for entries that expose that field), keep the raw nullable value in the
returned object (retain id, isPlatformGateway, gatewayTypeDisplay) and ensure
the UI rendering only shows the status chip when gatewayStatus is non-null;
apply the same fix to the other similar blocks referenced (around the other
ranges 172-181 and 254-263).
- Around line 60-83: The click handler in GatewayEditButton currently falls back
to dataRow.id when building the platform-gateway route; remove that fallback so
we only navigate to /settings/environments/platform-gateways/:id when
additionalProperties.platformGatewayId exists. In handleClick (and the second
identical block around lines 97-110), call
getAdditionalPropertiesAsMap(dataRow.additionalProperties), read
platformGatewayId without defaulting to dataRow.id, and if platformGatewayId
push the platform-gateways path, otherwise push the regular environment edit
path (/settings/environments/{dataRow.id}); update both occurrences of this
logic in GatewayEditButton to ensure consistent routing.

In `@portals/admin/src/main/webapp/source/src/app/data/api.js`:
- Around line 1185-1237: Add the missing requestContentType to the two gateway
methods: in getPlatformGatewayList() and deletePlatformGateway(gatewayId) ensure
the object passed to client.execute includes requestContentType:
'application/json' (same as createPlatformGateway() and
regeneratePlatformGatewayToken()), i.e., update the client.execute call inside
both getPlatformGatewayList and deletePlatformGateway to include
requestContentType: 'application/json'.

In `@portals/publisher/src/main/webapp/site/public/conf/settings.json`:
- Line 53: Update the supportedApiTypes array in settings.json so it includes
the three missing API types used by the codebase constants: add "WEBSUB",
"WEBHOOK", and "ASYNC" to the existing array in the settings.json file (the
supportedApiTypes entry) so it matches the full set of API types used by the
policy filtering logic.
- Around line 50-54: The policyHub configuration in settings.json contains a
hardcoded development URL in the "policyHub.endpoint" value; replace this with
an environment-specific mechanism (e.g., read process env like
POLICY_HUB_ENDPOINT at startup or use environment-specific files such as
settings.prod.json/settings.dev.json) and ensure whatever config loader the app
uses reads that env var or loads the correct file before enabling the
"policyHub" feature; update any deployment docs to require setting
POLICY_HUB_ENDPOINT (or including settings.prod.json) and change uses of
policyHub.endpoint to the resolved runtime value.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/CreateAPIWithAI/APICreateWithAI.jsx`:
- Around line 93-97: The gatewayDetails map currently only contains the
'platform-gateway' key which causes lookups from settings.environment (where
gatewayType may be 'PlatformGateway') to miss and create a custom gateway;
update the gatewayDetails map (the object named gatewayDetails in
APICreateWithAI.jsx) to also include a 'PlatformGateway' entry mapping to the
same value/name/description as 'platform-gateway' or normalize the gatewayType
value (e.g., lowercase/slugify) before performing the lookup in the code that
reads settings.environment; apply the same change or normalization in
APICreateRoutes.jsx where a similar lookup occurs (around the lookup at line 77)
so both components resolve the Platform Gateway identifier consistently.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyCard.tsx`:
- Around line 64-65: The code currently sets isPolicyHubGatewayPolicy from
policyObj.supportedGateways
(policyObj.supportedGateways?.includes(CONSTS.GATEWAY_TYPE.apiPlatform)) which
checks capability metadata instead of the active API gateway; change the logic
to derive the flag from the current API's gatewayType (use the same field used
by the drawer, e.g., api.gatewayType or the API prop available in
AttachedPolicyCard) and update both occurrences (the isPolicyHubGatewayPolicy
declaration and the identical check at the later location around line 157) so
showDownload and related behavior use the actual API gateway (compare
api.gatewayType === CONSTS.GATEWAY_TYPE.apiPlatform) rather than
policyObj.supportedGateways.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/Policies.tsx`:
- Around line 241-242: The else-if branch setting gatewayType =
CONSTS.GATEWAY_TYPE.apiPlatform is unreachable because isPolicyHubGateway is
returned early; remove this dead branch from the Policies component logic (look
for the isPolicyHubGateway check and the gatewayType assignment near the current
conditional) or refactor the surrounding conditional so gatewayType
determination is mutually exclusive and consistent with isPolicyHubGateway
(update any code that reads gatewayType accordingly).

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PoliciesExpansion.tsx`:
- Around line 111-119: The current lookup in PoliciesExpansion.tsx only calls
PolicyHub.getPolicySpec when both policyName and policyVersion exist, causing
attachments with a missing ApiPolicy.policyVersion to be dropped; update the
logic to attempt a lookup by name alone when policyVersion is missing (call
PolicyHub.getPolicySpec with { name: policyName, displayName: policyName } or
similar) and if that still yields null, create and return a minimal placeholder
policy object (with name/displayName and a flag like isPlaceholder) so the
attachment remains visible in allPolicies/flow rendering; ensure you update any
callers that rely on version to handle the placeholder case.
- Around line 122-128: The fallback in PoliciesExpansion.tsx always calls
API.getOperationPolicy(...) which prevents resolving attached common policies;
update the logic to call API.getCommonOperationPolicy(...) when the policy is
known to originate from common policies (use the same flag name isCommonPolicy
used by callers that populate originatedFromCommonPolicies). Modify the call
sites that push into originatedFromCommonPolicies to pass isCommonPolicy through
to the place that resolves the policy, and inside PoliciesExpansion (the
function that currently calls API.getOperationPolicy and returns
policyResponse?.body) choose API.getCommonOperationPolicy(policyId, api.id) when
isCommonPolicy is true, otherwise keep API.getOperationPolicy; ensure the
resolved body handling remains the same.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyConfigurationEditDrawer.tsx`:
- Around line 104-116: When loading the policy spec in
PolicyConfigurationEditDrawer (inside the isPolicyHubGateway branch where
PolicyHub.getPolicySpec is called), ensure you fall back to
PolicyHub.toPolicySpec(policyObj) if PolicyHub.getPolicySpec(...) returns
undefined/null; i.e., after awaiting PolicyHub.getPolicySpec({ name:
policyObj.name, version: policyObj.version, displayName: policyObj.displayName
}) assign policySpecVal = result ?? PolicyHub.toPolicySpec(policyObj) so
migrated/deleted policies render, matching the behavior in ViewPolicy.tsx.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/GeneralDetails.tsx`:
- Around line 271-299: The TextField for the description in GeneralDetails.tsx
incorrectly reuses id='name', causing duplicate DOM ids; update the TextField's
id to a unique value (e.g., 'description') so it matches the name/data-testid
and preserves proper label association and accessibility; ensure the change is
applied on the TextField with props value={description},
onChange={handleInputChange} and inputProps referencing isViewMode so behavior
remains unchanged.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/SourceDetails.tsx`:
- Around line 89-90: supportedGateways can be undefined, so first normalize it
(e.g., const normalizedSupportedGateways = Array.isArray(supportedGateways) ?
supportedGateways : []) and then replace usages of
supportedGateways.includes(...) with normalizedSupportedGateways.includes(...);
specifically update the computation of isPolicyHubGatewayPolicy (currently using
supportedGateways.includes(CONSTS.GATEWAY_TYPE.apiPlatform)) and reuse
normalizedSupportedGateways for the later checks referenced in this component
(the other .includes calls around Lines 94 and 257).

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`:
- Around line 223-227: The renderValue callback in PolicyList (the
renderValue={(selected) => ...} block) returns a hardcoded 'All' string; replace
that literal with an i18n lookup by importing and using useIntl in the
PolicyList component and calling intl.formatMessage({ id:
'Apis.Details.Policies.All', defaultMessage: 'All' }) (or use <FormattedMessage
/> equivalent) so the fallback text is internationalized; ensure useIntl is
invoked in the component and the renderValue path uses intl.formatMessage
instead of the hardcoded 'All'.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx`:
- Around line 60-80: When starting the Policy Hub fetch inside the dialogOpen &&
isPolicyHubGateway branch, clear any stale policySpec up front by calling
setPolicySpec(undefined) (or the empty initial state) before setLoading(true)
and PolicyHub.getPolicySpec; then in the .catch handler,
setPolicySpec(PolicyHub.toPolicySpec(policyObj)) as the local fallback instead
of leaving the previous spec, and keep Alert.error for the user-facing
message—this ensures PolicyHub.getPolicySpec (and functions
PolicyHub.getPolicySpec, PolicyHub.toPolicySpec, setPolicySpec, setLoading,
Alert.error) cannot leave stale data displayed if the fetch fails.

In `@portals/publisher/src/main/webapp/source/src/app/data/Constants.js`:
- Around line 85-89: The project uses two different serialized identifiers for
the same gateway ('platform-gateway' vs the new CONSTS.GATEWAY_TYPE.apiPlatform
= 'api-platform'), causing policy flows to miss APIs; update APICreateRoutes.jsx
(the publisher create flow) and MetaData.jsx (the special-case logic) to emit
and check CONSTS.GATEWAY_TYPE.apiPlatform instead of the literal
'platform-gateway', and update PoliciesExpansion.tsx and PolicyViewForm.tsx to
also accept a fallback for existing records (treat 'platform-gateway' equivalent
to CONSTS.GATEWAY_TYPE.apiPlatform) so both new and existing APIs map to the
same Policy Hub path.

---

Nitpick comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`:
- Around line 124-153: The effect reads selectedCategories but doesn't include
it in dependencies, risking stale closures and ESLint warnings; update the logic
in the useEffect (the useEffect that references availableCategories, isAIAPI,
isRestAPI, selectedCategories, and calls setSelectedCategories) to use a
functional state update: call setSelectedCategories(prev => { compute
normalizedSelection, nextSelection and compare against prev, returning prev or
the new array }) so the current selectedCategories value is read from prev
instead of the closed-over variable; alternatively, if you prefer not to use a
functional updater, add selectedCategories to the dependency array of the
useEffect to satisfy exhaustive-deps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a0f9a37-e6f9-46cb-a308-801bcfe4c283

📥 Commits

Reviewing files that changed from the base of the PR and between ce57ec5 and e2d40f3.

⛔ Files ignored due to path filters (1)
  • portals/admin/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • portals/admin/src/main/webapp/site/public/conf/settings.json
  • portals/admin/src/main/webapp/site/public/locales/en.json
  • portals/admin/src/main/webapp/site/public/locales/fr.json
  • portals/admin/src/main/webapp/source/src/app/components/AdminPages/Addons/ListBase.jsx
  • portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/ListGWEnviornments.jsx
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/index.jsx
  • portals/admin/src/main/webapp/source/src/app/data/api.js
  • portals/publisher/src/main/webapp/site/public/conf/settings.json
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/CreateAPIWithAI/APICreateWithAI.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/NewOverview/MetaData.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyCard.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/Policies.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PoliciesExpansion.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyConfigurationEditDrawer.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/GeneralDetails.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/PolicyViewForm.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/SourceDetails.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/Types.d.ts
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Shared/PoliciesUI/AttachedPolicyCard.tsx
  • portals/publisher/src/main/webapp/source/src/app/data/Constants.js
  • portals/publisher/src/main/webapp/source/src/app/data/defaultTheme.js

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx (2)

169-170: Clarify empty selection behavior with a comment.

The shouldShowPolicy function returns true when selectedCategories.length === 0, meaning "show all". This behavior is correct but not immediately obvious.

Suggested documentation
+    // When no categories are selected, show all policies (unfiltered)
     const shouldShowPolicy = (policy: Policy) =>
         selectedCategories.length === 0 || selectedCategories.includes(getPolicyCategory(policy));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`
around lines 169 - 170, Add a clarifying comment above the shouldShowPolicy
function explaining that an empty selectedCategories array is treated as "show
all" (i.e., selectedCategories.length === 0 returns true), so the function
returns true for all policies when nothing is selected; reference the function
name shouldShowPolicy and the helper getPolicyCategory so reviewers can locate
and understand the intent.

125-154: Category defaults initialization is well-designed but complex.

The useEffect correctly:

  1. Filters out invalid categories when availableCategories changes
  2. Applies API-type-specific defaults (AI/Guardrails for AIAPI, Transformation/Security for REST)
  3. Uses referential equality check (Lines 148-152) to prevent unnecessary re-renders

Consider adding a brief comment explaining the default selection logic for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`
around lines 125 - 154, Add a short inline comment inside the useEffect above
the default-selection block explaining the intent and rules for default
categories: that when availableCategories is non-empty we filter invalid
selections and, if nothing remains, we pick API-type-specific defaults (for
isAIAPI choose ['AI','Guardrails'], for isRestAPI choose
['Transformation','Security']) only if those defaults are present in
availableCategories—this comment should be placed near the setSelectedCategories
callback (referencing useEffect, setSelectedCategories, availableCategories,
isAIAPI, isRestAPI) so future maintainers understand the fallback/default logic
without changing any behavior.
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx (1)

124-124: Consider adding isPolicyHubGateway to the useEffect dependency array.

The effect reads isPolicyHubGateway but the dependency array only includes dialogOpen. If api.gatewayType changes while the dialog is open, the effect won't re-run with the correct branch logic.

Suggested fix
-    }, [dialogOpen]);
+    }, [dialogOpen, isPolicyHubGateway]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx`
at line 124, The useEffect in ViewPolicy currently depends only on dialogOpen
but also reads isPolicyHubGateway (derived from api.gatewayType), so add
isPolicyHubGateway to the dependency array so the effect re-runs when
api.gatewayType changes; locate the useEffect that references dialogOpen and
isPolicyHubGateway and include isPolicyHubGateway (or the underlying
api.gatewayType) in its dependency list to ensure the branch logic updates
correctly while the dialog is open.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@portals/admin/src/main/webapp/site/public/locales/fr.json`:
- Line 160: Several locale entries in the French locale are still in English;
update the keys such as "AdminPages.Gateways.table.header.gatewayStatus" and the
other untranslated keys referenced (around the other ranges) with proper French
translations, e.g., replace the English strings with their French equivalents
for all entries in fr.json (including the blocks around the reported offsets 382
and 509-536); ensure you only change the string values (keep the JSON keys
intact), validate the JSON, and run a quick UI spot-check of the gateway screens
to confirm translations render correctly.

In
`@portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx`:
- Around line 221-236: The fallback platform-gateway object sets no isActive but
downstream rendering treats falsy as "Inactive"; update the fallback created in
the platformGateway lookup (where platformGatewayId is used and
setPlatformGateway is called) to include isActive: null (or undefined) instead
of leaving it ambiguous so the header can render an explicit "Unknown" or hide
the chip; make the same change for the other similar fallback creation (the
other setPlatformGateway call referenced in the diff) so unresolved gateways
have a nullable isActive rather than being interpreted as inactive.

In
`@portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/ListGWEnviornments.jsx`:
- Around line 93-109: The row mapping currently sets human-readable English
strings into gatewayTypeDisplay and gatewayStatus (e.g., 'Platform Gateway',
'Active', 'Inactive'); instead, keep raw values in the model so the renderer can
localize with intl.formatMessage: set gatewayTypeDisplay to a stable token/enum
(e.g., item.gatewayType or 'platform' for platform gateways) and set
gatewayStatus to a raw status flag (e.g., additionalProperties.isActive as
boolean or 'true'/'false' or a constant like 'ACTIVE'/'INACTIVE'), remove
hard-coded English text from the map block inside the
environmentResult.body.list .map (refer to the isPlatformGateway,
platformGatewayId, gatewayTypeDisplay and gatewayStatus symbols) and apply the
same change for the other mapping at 170-181 so the UI renderer can call
intl.formatMessage() to produce localized labels.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/GeneralDetails.tsx`:
- Around line 98-104: Update the static section summary text in GeneralDetails
so it respects the hideFlowsAndApiTypes flag: when hideFlowsAndApiTypes is true,
replace or neutralize the sentence that prompts users to "provide applicable
flows" (or similar) and instead show copy that does not reference Applicable
Flows or Supported API Types; use the existing booleans showApplicableFlows /
showSupportedApiTypes (or directly hideFlowsAndApiTypes) to decide which copy to
render so the summary matches the visible fields.

---

Nitpick comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`:
- Around line 169-170: Add a clarifying comment above the shouldShowPolicy
function explaining that an empty selectedCategories array is treated as "show
all" (i.e., selectedCategories.length === 0 returns true), so the function
returns true for all policies when nothing is selected; reference the function
name shouldShowPolicy and the helper getPolicyCategory so reviewers can locate
and understand the intent.
- Around line 125-154: Add a short inline comment inside the useEffect above the
default-selection block explaining the intent and rules for default categories:
that when availableCategories is non-empty we filter invalid selections and, if
nothing remains, we pick API-type-specific defaults (for isAIAPI choose
['AI','Guardrails'], for isRestAPI choose ['Transformation','Security']) only if
those defaults are present in availableCategories—this comment should be placed
near the setSelectedCategories callback (referencing useEffect,
setSelectedCategories, availableCategories, isAIAPI, isRestAPI) so future
maintainers understand the fallback/default logic without changing any behavior.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx`:
- Line 124: The useEffect in ViewPolicy currently depends only on dialogOpen but
also reads isPolicyHubGateway (derived from api.gatewayType), so add
isPolicyHubGateway to the dependency array so the effect re-runs when
api.gatewayType changes; locate the useEffect that references dialogOpen and
isPolicyHubGateway and include isPolicyHubGateway (or the underlying
api.gatewayType) in its dependency list to ensure the branch logic updates
correctly while the dialog is open.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0357ae8d-8e53-4887-b6c6-035948c10750

📥 Commits

Reviewing files that changed from the base of the PR and between e2d40f3 and a88d398.

📒 Files selected for processing (19)
  • portals/admin/src/main/webapp/site/public/locales/en.json
  • portals/admin/src/main/webapp/site/public/locales/fr.json
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/ListGWEnviornments.jsx
  • portals/admin/src/main/webapp/source/src/app/data/api.js
  • portals/publisher/src/main/webapp/site/public/conf/settings.json
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/site/public/locales/fr.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/CreateAPIWithAI/APICreateWithAI.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/NewOverview/MetaData.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyCard.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/Policies.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PoliciesExpansion.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyConfigurationEditDrawer.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/GeneralDetails.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/SourceDetails.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • portals/admin/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/NewOverview/MetaData.jsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyCard.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/CreateAPIWithAI/APICreateWithAI.jsx
  • portals/publisher/src/main/webapp/site/public/locales/en.json

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js (5)

10-13: Hardcoded default endpoint exposes internal infrastructure.

The default Policy Hub endpoint appears to be a development/staging URL. Consider either removing the default or using a placeholder that clearly indicates configuration is required.

Proposed fix
-const DEFAULT_POLICY_HUB_ENDPOINT =
-    'https://db720294-98fd-40f4-85a1-cc6a3b65bc9a-dev.e1-us-east-azure.choreoapis.dev' +
-    '/api-platform/policy-hub-api/policy-hub-public/v1.0';
+const DEFAULT_POLICY_HUB_ENDPOINT = '';

Then update getPolicyHubEndpoint to throw or warn if no endpoint is configured:

 const getPolicyHubEndpoint = () => {
     const configuredEndpoint = Configurations?.app?.policyHub?.endpoint;
-    const endpoint = configuredEndpoint || DEFAULT_POLICY_HUB_ENDPOINT;
+    if (!configuredEndpoint) {
+        console.warn('Policy Hub endpoint is not configured');
+    }
+    const endpoint = configuredEndpoint || '';
     return endpoint.replace(/\/$/, '');
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 10 - 13, Replace the hardcoded development URL in
DEFAULT_POLICY_HUB_ENDPOINT with a non-sensitive placeholder or null (e.g., a
clearly marked placeholder string or undefined) and update getPolicyHubEndpoint
to detect the missing config and either throw an error or log a warning
instructing the deployer to set the real endpoint; specifically change the
DEFAULT_POLICY_HUB_ENDPOINT constant and add validation in the
getPolicyHubEndpoint function to fail fast (throw or console.warn) when no
endpoint is configured so no internal/staging URL is used by default.

309-328: Add iteration safeguard to pagination loop.

If the API returns an incorrect or very large count, this loop could make excessive requests. Consider adding a maximum iteration limit.

Proposed enhancement
 const listAllPolicies = async () => {
     const limit = getPolicyHubLimit();
     let offset = 0;
     let count = 0;
     const policies = [];
+    const maxIterations = 100; // Safety limit
+    let iterations = 0;

     do {
+        if (iterations++ >= maxIterations) {
+            console.warn('listAllPolicies: Maximum iterations reached');
+            break;
+        }
         // eslint-disable-next-line no-await-in-loop
         const response = await listPolicies({ offset, limit });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 309 - 328, The pagination loop in listAllPolicies can run indefinitely if
the API returns a wrong/huge count; add a safeguard by introducing a max
iteration/pages guard (e.g., MAX_ITERATIONS or MAX_PAGES) and an iterations
counter, increment it each loop and break (or throw a descriptive error) if
exceeded; update references in listAllPolicies (limit, offset, count, policies,
and the call to listPolicies) so the loop stops after that cap to prevent
excessive requests.

265-294: Add timeout to prevent indefinitely hanging requests.

The fetch call has no timeout configured. Network issues could cause the UI to hang indefinitely while waiting for Policy Hub responses.

Proposed fix using AbortController
-const request = async (path, { method = 'GET', params, headers, body } = {}) => {
+const DEFAULT_TIMEOUT_MS = 30000;
+
+const request = async (path, { method = 'GET', params, headers, body, timeout = DEFAULT_TIMEOUT_MS } = {}) => {
     const endpoint = getPolicyHubEndpoint();
     const url = new URL(`${endpoint}${path}`);

     if (params) {
         Object.entries(params).forEach(([key, value]) => {
             if (value !== undefined && value !== null && value !== '') {
                 url.searchParams.append(key, value);
             }
         });
     }

+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), timeout);
+
-    const response = await fetch(url.toString(), {
+    let response;
+    try {
+        response = await fetch(url.toString(), {
             method,
             headers: {
                 Accept: 'application/json',
                 ...(headers || {}),
             },
             body,
+            signal: controller.signal,
         });
+    } finally {
+        clearTimeout(timeoutId);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 265 - 294, The request function currently calls fetch without a timeout;
modify request (the async function named request) to use an AbortController:
create a controller, pass controller.signal into fetch, start a timer (e.g.,
from a configurable timeout value or a sensible default) that calls
controller.abort() when elapsed, and clear the timer after fetch completes;
handle aborts by catching the thrown DOMException/AbortError and rethrowing a
clear Error (e.g., "Policy Hub request timed out") while preserving existing
error handling for non-OK responses; ensure headers/body/params usage remains
unchanged and the abort controller is cleaned up to avoid leaks.

94-115: Consider adding recursion depth limit to prevent stack overflow.

findAttributeArray and findSchemaObject recursively traverse objects without depth limits. Malformed or malicious policy definitions could cause stack overflow.

Proposed enhancement
-const findAttributeArray = (obj) => {
+const findAttributeArray = (obj, depth = 0, maxDepth = 20) => {
-    if (!obj || typeof obj !== 'object') {
+    if (!obj || typeof obj !== 'object' || depth > maxDepth) {
         return null;
     }
     if (isAttributeArray(obj)) {
         return obj;
     }
     for (const value of Object.values(obj)) {
         if (isAttributeArray(value)) {
             return value;
         }
     }
     for (const value of Object.values(obj)) {
         if (value && typeof value === 'object') {
-            const nested = findAttributeArray(value);
+            const nested = findAttributeArray(value, depth + 1, maxDepth);
             if (nested) {
                 return nested;
             }
         }
     }
     return null;
 };

Apply similar changes to findSchemaObject.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 94 - 115, The recursive traversals in findAttributeArray (and similarly in
findSchemaObject) need a recursion depth limit to avoid stack overflows on
malformed inputs; add an optional depth parameter (or use a local MAX_DEPTH
constant) and on each recursive call decrement/check it, returning null when
depth is exceeded, and update every recursive invocation inside
findAttributeArray and findSchemaObject to pass the decremented depth so
traversal stops after the limit; ensure the default behavior uses a sensible
MAX_DEPTH (e.g., 50) so existing behavior is preserved for normal inputs.

16-17: Unbounded in-memory cache may cause memory issues.

The module-level policyDefinitionCache and policySpecCache Maps have no size limit or TTL. In long-running sessions with many policies, memory usage could grow unbounded and cached data may become stale.

Consider implementing a simple LRU cache with size limits, or at minimum adding a manual cache clear function to the exports:

const clearCaches = () => {
    policyDefinitionCache.clear();
    policySpecCache.clear();
};

export default {
    // ... existing exports
    clearCaches,
};

Also applies to: 354-384

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 16 - 17, The module-level Maps policyDefinitionCache and policySpecCache
are unbounded and risk memory growth; add a mitigation by either replacing them
with a bounded LRU implementation (e.g., a small LRU class used for
policyDefinitionCache and policySpecCache) or, minimally, add and export a
clearCaches function that calls policyDefinitionCache.clear() and
policySpecCache.clear(); update the module's default export (and any referenced
functions that assume caches) to include clearCaches so callers can purge
caches, and ensure any places noted around lines 354-384 use the new cache API
consistently.
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx (2)

174-180: Consider memoizing filtered policy lists.

filterPoliciesForFlow is called multiple times during render. For large policy lists, consider memoizing the filtered results to avoid repeated filtering on each render.

Example using useMemo
const filteredRequestPolicies = useMemo(
    () => ({
        common: filterPoliciesForFlow(commonPolicyList, 'request'),
        api: filterPoliciesForFlow(apiPolicyList, 'request'),
    }),
    [commonPolicyList, apiPolicyList, selectedCategories, gatewayType]
);
// Similar for response and fault flows
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`
around lines 174 - 180, filterPoliciesForFlow is being run repeatedly during
render; wrap the filtered results in useMemo so the arrays are recomputed only
when inputs change. Replace repeated calls to filterPoliciesForFlow for each
flow (e.g., 'request', 'response', 'fault') and each source list
(commonPolicyList, apiPolicyList) with memoized values using useMemo, and
include relevant dependencies like commonPolicyList, apiPolicyList, gatewayType
and selectedCategories so the cache invalidates correctly; update places that
read those filtered lists to use the memoized variables.

149-152: Array comparison may miss order differences.

The comparison logic checks if arrays have the same length and same elements at each index, but nextSelection may have elements in a different order than prev. This could cause unnecessary re-renders or miss updates when order differs but content is the same.

Consider using Set-based comparison or sorting
-            if (nextSelection.length !== prev.length
-                || nextSelection.some((category, index) => category !== prev[index])) {
+            const prevSet = new Set(prev);
+            const nextSet = new Set(nextSelection);
+            const areEqual = prevSet.size === nextSet.size 
+                && [...nextSet].every((cat) => prevSet.has(cat));
+            if (!areEqual) {
                 return nextSelection;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`
around lines 149 - 152, The current equality check comparing nextSelection and
prev by index can miss cases where they contain the same elements in a different
order; update the logic around the array comparison (the block that checks
nextSelection.length !== prev.length || nextSelection.some(...)) to perform an
order-insensitive comparison instead—either convert both arrays to Sets and
compare sizes and that every element of one Set exists in the other, or sort
both arrays (stable, same comparator) and then compare elements; ensure you
still return nextSelection when they differ. Use the existing identifiers
nextSelection and prev to locate and replace the comparison.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/PlatformGatewayManagement.jsx`:
- Around line 634-655: The async handlers (handleRoleAddition and similarly
handleRoleDeletion) suffer from stale closure updates on
validRoles/invalidRoles; change all state updates inside their promise
resolution/rejection to use functional state setters (e.g., setValidRoles(prev
=> [...prev, role]) and setInvalidRoles(prev => [...prev, role]) and compute
roleValidity from the current prev values) so each async callback operates on
the latest state rather than captured arrays; update every setValidRoles,
setInvalidRoles and setRoleValidity call in handleRoleAddition (and mirror the
pattern in handleRoleDeletion) to use the functional updater form and derive
roleValidity from the up-to-date prev state.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/GeneralDetails.tsx`:
- Around line 163-166: The handler handleApiTypeChange currently references
isWebsocketSelected before it's declared, causing a temporal dead zone error;
move the declaration/initialization of the isWebsocketSelected variable so it
appears above (or at least before) the handleApiTypeChange function definition,
then update any other usages (e.g., the similar block around lines 175-178) to
reference the already-initialized isWebsocketSelected; ensure
handleApiTypeChange and any related logic read the variable only after it's
defined.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js`:
- Around line 31-40: parsePolicyId fails when the policy name contains
POLICY_ID_SEPARATOR because splitting on the separator yields >2 parts; update
parsePolicyId to locate the last occurrence of POLICY_ID_SEPARATOR (e.g., via
lastIndexOf) and split into name = substring before that index and version =
substring after, preserving any separators in the name, then validate non-empty
name/version and return {name, version} or null; reference the parsePolicyId
function and POLICY_ID_SEPARATOR when making the change.

---

Nitpick comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx`:
- Around line 174-180: filterPoliciesForFlow is being run repeatedly during
render; wrap the filtered results in useMemo so the arrays are recomputed only
when inputs change. Replace repeated calls to filterPoliciesForFlow for each
flow (e.g., 'request', 'response', 'fault') and each source list
(commonPolicyList, apiPolicyList) with memoized values using useMemo, and
include relevant dependencies like commonPolicyList, apiPolicyList, gatewayType
and selectedCategories so the cache invalidates correctly; update places that
read those filtered lists to use the memoized variables.
- Around line 149-152: The current equality check comparing nextSelection and
prev by index can miss cases where they contain the same elements in a different
order; update the logic around the array comparison (the block that checks
nextSelection.length !== prev.length || nextSelection.some(...)) to perform an
order-insensitive comparison instead—either convert both arrays to Sets and
compare sizes and that every element of one Set exists in the other, or sort
both arrays (stable, same comparator) and then compare elements; ensure you
still return nextSelection when they differ. Use the existing identifiers
nextSelection and prev to locate and replace the comparison.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js`:
- Around line 10-13: Replace the hardcoded development URL in
DEFAULT_POLICY_HUB_ENDPOINT with a non-sensitive placeholder or null (e.g., a
clearly marked placeholder string or undefined) and update getPolicyHubEndpoint
to detect the missing config and either throw an error or log a warning
instructing the deployer to set the real endpoint; specifically change the
DEFAULT_POLICY_HUB_ENDPOINT constant and add validation in the
getPolicyHubEndpoint function to fail fast (throw or console.warn) when no
endpoint is configured so no internal/staging URL is used by default.
- Around line 309-328: The pagination loop in listAllPolicies can run
indefinitely if the API returns a wrong/huge count; add a safeguard by
introducing a max iteration/pages guard (e.g., MAX_ITERATIONS or MAX_PAGES) and
an iterations counter, increment it each loop and break (or throw a descriptive
error) if exceeded; update references in listAllPolicies (limit, offset, count,
policies, and the call to listPolicies) so the loop stops after that cap to
prevent excessive requests.
- Around line 265-294: The request function currently calls fetch without a
timeout; modify request (the async function named request) to use an
AbortController: create a controller, pass controller.signal into fetch, start a
timer (e.g., from a configurable timeout value or a sensible default) that calls
controller.abort() when elapsed, and clear the timer after fetch completes;
handle aborts by catching the thrown DOMException/AbortError and rethrowing a
clear Error (e.g., "Policy Hub request timed out") while preserving existing
error handling for non-OK responses; ensure headers/body/params usage remains
unchanged and the abort controller is cleaned up to avoid leaks.
- Around line 94-115: The recursive traversals in findAttributeArray (and
similarly in findSchemaObject) need a recursion depth limit to avoid stack
overflows on malformed inputs; add an optional depth parameter (or use a local
MAX_DEPTH constant) and on each recursive call decrement/check it, returning
null when depth is exceeded, and update every recursive invocation inside
findAttributeArray and findSchemaObject to pass the decremented depth so
traversal stops after the limit; ensure the default behavior uses a sensible
MAX_DEPTH (e.g., 50) so existing behavior is preserved for normal inputs.
- Around line 16-17: The module-level Maps policyDefinitionCache and
policySpecCache are unbounded and risk memory growth; add a mitigation by either
replacing them with a bounded LRU implementation (e.g., a small LRU class used
for policyDefinitionCache and policySpecCache) or, minimally, add and export a
clearCaches function that calls policyDefinitionCache.clear() and
policySpecCache.clear(); update the module's default export (and any referenced
functions that assume caches) to include clearCaches so callers can purge
caches, and ensure any places noted around lines 354-384 use the new cache API
consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb7b4111-fe75-4d9a-a059-41f1e8bcb19f

📥 Commits

Reviewing files that changed from the base of the PR and between a88d398 and 62f3cb0.

📒 Files selected for processing (12)
  • portals/admin/src/main/webapp/site/public/locales/fr.json
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/ListGWEnviornments.jsx
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/PlatformGatewayManagement.jsx
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/Policies.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PoliciesExpansion.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/GeneralDetails.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyForm/SourceDetails.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/PolicyList.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx
  • portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • portals/admin/src/main/webapp/source/src/app/components/GatewayEnvironments/AddEditGWEnvironment.jsx
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/admin/src/main/webapp/site/public/locales/fr.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/ViewPolicy.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
portals/admin/src/main/webapp/site/public/locales/fr.json (1)

160-160: ⚠️ Potential issue | 🟡 Minor

Translate the new Platform Gateway strings in fr.json.

These entries are still English, so French users will see untranslated copy throughout the new Platform Gateway flow. Please replace the new values with actual French strings before merging.

Also applies to: 382-382, 508-536

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/admin/src/main/webapp/site/public/locales/fr.json` at line 160, The
fr.json locale contains untranslated English strings for the new Platform
Gateway flow (e.g. the key "AdminPages.Gateways.table.header.gatewayStatus");
update these entries by replacing the English values with correct French
translations for all new Platform Gateway keys in fr.json (including the other
untranslated entries noted in the review), ensure JSON remains valid (proper
quoting/escaping) and keep the keys unchanged so the UI picks up the
translations.
🧹 Nitpick comments (1)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx (1)

78-89: Consider adding a comment explaining the dual entries.

Based on the context in MetaData.jsx, the backend can return either 'api-platform' or 'PlatformGateway' as the gateway type. Having both keys in gatewayDetails correctly handles this, but future maintainers may not understand why two entries exist for the same gateway.

📝 Suggested clarifying comment
     },
+    // Two entries for Platform Gateway: the canonical constant key ('api-platform') and
+    // the literal key ('PlatformGateway') to handle both formats returned by the backend.
     [CONSTS.GATEWAY_TYPE.apiPlatform]: {
         value: CONSTS.GATEWAY_TYPE.apiPlatform,
         name: 'Platform Gateway',
         description: 'API gateway for platform-managed policies.',
         isNew: false,
     },
     PlatformGateway: {
         value: CONSTS.GATEWAY_TYPE.apiPlatform,
         name: 'Platform Gateway',
         description: 'API gateway for platform-managed policies.',
         isNew: false,
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx`
around lines 78 - 89, The two identical entries in gatewayDetails (the key
CONSTS.GATEWAY_TYPE.apiPlatform and the PlatformGateway key) exist intentionally
because the backend can return either 'api-platform' or 'PlatformGateway' (see
MetaData.jsx); add a concise inline comment above these entries explaining that
both keys map to the same gateway value to handle legacy/alternate backend
responses, referencing CONSTS.GATEWAY_TYPE.apiPlatform, PlatformGateway and
gatewayDetails so future maintainers understand why both entries are present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@portals/admin/src/main/webapp/site/public/locales/fr.json`:
- Line 160: The fr.json locale contains untranslated English strings for the new
Platform Gateway flow (e.g. the key
"AdminPages.Gateways.table.header.gatewayStatus"); update these entries by
replacing the English values with correct French translations for all new
Platform Gateway keys in fr.json (including the other untranslated entries noted
in the review), ensure JSON remains valid (proper quoting/escaping) and keep the
keys unchanged so the UI picks up the translations.

---

Nitpick comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx`:
- Around line 78-89: The two identical entries in gatewayDetails (the key
CONSTS.GATEWAY_TYPE.apiPlatform and the PlatformGateway key) exist intentionally
because the backend can return either 'api-platform' or 'PlatformGateway' (see
MetaData.jsx); add a concise inline comment above these entries explaining that
both keys map to the same gateway value to handle legacy/alternate backend
responses, referencing CONSTS.GATEWAY_TYPE.apiPlatform, PlatformGateway and
gatewayDetails so future maintainers understand why both entries are present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e07ed824-d046-4242-b80c-e7a05988d13c

📥 Commits

Reviewing files that changed from the base of the PR and between 62f3cb0 and 82727e4.

📒 Files selected for processing (5)
  • portals/admin/src/main/webapp/site/public/locales/en.json
  • portals/admin/src/main/webapp/site/public/locales/fr.json
  • portals/devportal/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Create/APICreateRoutes.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • portals/publisher/src/main/webapp/site/public/locales/en.json

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js (2)

267-296: Consider adding request timeout to prevent indefinite hangs.

The fetch call has no timeout. If the Policy Hub server becomes unresponsive, requests will hang indefinitely, potentially blocking UI interactions.

Proposed fix using AbortController
+const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
+
 const request = async (path, { method = 'GET', params, headers, body } = {}) => {
     const endpoint = getPolicyHubEndpoint();
     const url = new URL(`${endpoint}${path}`);

     if (params) {
         Object.entries(params).forEach(([key, value]) => {
             if (value !== undefined && value !== null && value !== '') {
                 url.searchParams.append(key, value);
             }
         });
     }

+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), DEFAULT_REQUEST_TIMEOUT_MS);
+
-    const response = await fetch(url.toString(), {
+    let response;
+    try {
+        response = await fetch(url.toString(), {
-        method,
-        headers: {
-            Accept: 'application/json',
-            ...(headers || {}),
-        },
-        body,
-    });
+            method,
+            headers: {
+                Accept: 'application/json',
+                ...(headers || {}),
+            },
+            body,
+            signal: controller.signal,
+        });
+    } finally {
+        clearTimeout(timeoutId);
+    }

     if (!response.ok) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 267 - 296, The request function can hang because fetch has no timeout;
modify request (in PolicyHub.js) to use an AbortController with a configurable
timeout (e.g., default 10s) so the fetch is aborted after the timer elapses:
create an AbortController, pass its signal into fetch options, set a setTimeout
to call controller.abort() and clear the timeout on success, and ensure the
thrown error on abort is handled/annotated (preserve existing error handling for
response.ok). Keep the existing params/headers/body behavior and add an optional
timeout argument to request or use an internal constant.

18-19: Consider adding cache eviction for long-running sessions.

The in-memory caches grow unbounded. For most use cases this is acceptable, but in long-running sessions with many policy lookups, memory could accumulate.

A simple LRU cache or TTL-based eviction could be added later if this becomes an issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js` around
lines 18 - 19, The in-memory maps policyDefinitionCache and policySpecCache grow
unbounded; introduce eviction by replacing direct Map usage with a
capped/TTL-backed cache wrapper (e.g., an LRU or TTL policy) and update all
access points to use the new wrapper’s get/set/delete methods; specifically,
create a small cache utility (used by policyDefinitionCache and policySpecCache)
that enforces a maxEntries and/or timeToLive, evicts oldest or expired entries
on put/get, and exposes the same lookup/insert semantics so functions that
reference policyDefinitionCache and policySpecCache can remain unchanged except
for using the new cache instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js`:
- Around line 12-14: The constant DEFAULT_POLICY_HUB_ENDPOINT in PolicyHub.js is
a hardcoded Choreo dev URL that may leak into production; change the code that
reads settings.policyHub.endpoint to fail fast or at least warn instead of
silently using DEFAULT_POLICY_HUB_ENDPOINT: update the logic around
DEFAULT_POLICY_HUB_ENDPOINT and the settings lookup (policyHub.endpoint) to
throw an explicit error when no endpoint is configured (or emit a clear runtime
warning if you prefer), and document this behavior so deployments must provide
policyHub.endpoint; ensure the change references DEFAULT_POLICY_HUB_ENDPOINT and
the settings.policyHub.endpoint usage so reviewers can find and update all call
sites.

---

Nitpick comments:
In `@portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js`:
- Around line 267-296: The request function can hang because fetch has no
timeout; modify request (in PolicyHub.js) to use an AbortController with a
configurable timeout (e.g., default 10s) so the fetch is aborted after the timer
elapses: create an AbortController, pass its signal into fetch options, set a
setTimeout to call controller.abort() and clear the timeout on success, and
ensure the thrown error on abort is handled/annotated (preserve existing error
handling for response.ok). Keep the existing params/headers/body behavior and
add an optional timeout argument to request or use an internal constant.
- Around line 18-19: The in-memory maps policyDefinitionCache and
policySpecCache grow unbounded; introduce eviction by replacing direct Map usage
with a capped/TTL-backed cache wrapper (e.g., an LRU or TTL policy) and update
all access points to use the new wrapper’s get/set/delete methods; specifically,
create a small cache utility (used by policyDefinitionCache and policySpecCache)
that enforces a maxEntries and/or timeToLive, evicts oldest or expired entries
on put/get, and exposes the same lookup/insert semantics so functions that
reference policyDefinitionCache and policySpecCache can remain unchanged except
for using the new cache instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c47c625-022d-4d44-af43-1b103c425f54

📥 Commits

Reviewing files that changed from the base of the PR and between 82727e4 and 885a738.

📒 Files selected for processing (2)
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/NewOverview/MetaData.jsx
  • portals/publisher/src/main/webapp/source/src/app/data/PolicyHub.js

@AnuGayan
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

id: 'Base.RouteMenuMapping.gateways.items.Editing',
defaultMessage: 'Edit Gateway Environment',
}),
path: '/settings/gateways/(.*?)$',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we have been using incorrect paths here . Let's fix those under a separate issue

@ashera96
Copy link
Copy Markdown
Contributor

Let's verify the security issue picked by #1266 (comment)

Comment on lines -2 to +6
"name": "publisher",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

"name": "publisher",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove this diff?

@sonarqubecloud
Copy link
Copy Markdown

@AnuGayan AnuGayan merged commit 9962218 into wso2:main Mar 20, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants